package com.md.infrastructure.config;

import com.md.value.constants.NumberConstants;
import feign.MethodMetadata;
import feign.Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static feign.Util.checkState;
import static feign.Util.emptyToNull;
import static org.springframework.core.annotation.AnnotationUtils.synthesizeAnnotation;

/**
 * 继承SpringMvcContract 实现多继承
 * https://www.jianshu.com/p/c3a77ce07a1b
 *
 * @author 刘骄阳
 * 2022-07-02 15:07
 */
@Component
@Slf4j
public class CustomContract extends SpringMvcContract {

  private ResourceLoader resourceLoader;

  @Override
  public List<MethodMetadata> parseAndValidateMetadata(final Class<?> targetType) {
    // 校验类
    judgeClass(targetType);
    final Map<String, MethodMetadata> result = new LinkedHashMap<>();
    for (final Method method : targetType.getMethods()) {
      // 如果类是对象,或者含有静态方法,或者方法非公共方法,跳过
      if (method.getDeclaringClass() == Object.class || (method.getModifiers() & Modifier.STATIC) != 0
        || Util.isDefault(method)) {
        continue;
      }
      final MethodMetadata metadata = this.parseAndValidateMetadata(targetType, method);
      checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s", metadata.configKey());
      result.put(metadata.configKey(), metadata);
    }
    return new ArrayList<>(result.values());
  }

  @Override
  public MethodMetadata parseAndValidateMetadata(final Class<?> targetType, final Method method) {
    final MethodMetadata methodMetadata = super.parseAndValidateMetadata(targetType, method);

    final LinkedList<Class<?>> classHierarchy = new LinkedList<>();
    classHierarchy.add(targetType);
    this.findClass(targetType, method.getDeclaringClass(), classHierarchy);
    classHierarchy.stream()
      .map(this::processPathValue)
      .filter(Optional::isPresent)
      .map(Optional::get)
      .findFirst()
      .ifPresent(path -> methodMetadata.template().target(path));
    return methodMetadata;
  }

  private Optional<String> processPathValue(final Class<?> clz) {
    Optional<String> result = Optional.empty();
    final RequestMapping classAnnotation = clz.getAnnotation(RequestMapping.class);
    if (classAnnotation != null) {
      final RequestMapping synthesizeAnnotation = synthesizeAnnotation(classAnnotation, clz);
      // Prepend path from class annotation if specified
      if (synthesizeAnnotation.value().length > 0) {
        String pathValue = emptyToNull(synthesizeAnnotation.value()[0]);
        pathValue = this.resolveValue(pathValue);
        if (!pathValue.startsWith("/")) {
          pathValue = "/" + pathValue;
        }
        result = Optional.of(pathValue);
      }
    }
    return result;
  }

  private String resolveValue(final String value) {
    if (StringUtils.hasText(value) && this.resourceLoader instanceof ConfigurableApplicationContext) {
      return ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment().resolvePlaceholders(value);
    }
    return value;
  }

  @Override
  protected void processAnnotationOnClass(final MethodMetadata data, final Class<?> clz) {
    // skip this step
  }

  private boolean findClass(final Class<?> currentClass, final Class<?> searchClass,
                            final LinkedList<Class<?>> classHierarchy) {
    if (currentClass == searchClass) {
      return true;
    }
    final Class<?>[] interfaces = currentClass.getInterfaces();
    for (final Class<?> currentInterface : interfaces) {
      classHierarchy.add(currentInterface);
      final boolean findClass = this.findClass(currentInterface, searchClass, classHierarchy);
      if (findClass) {
        return true;
      }
      classHierarchy.removeLast();
    }
    return false;
  }

  @Override
  public void setResourceLoader(final ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
    super.setResourceLoader(resourceLoader);
  }

  /**
   * 校验类
   *
   * @param targetType 类
   */
  private void judgeClass(final Class<?> targetType) {
    // 检查类上面是否有泛型,如果有泛型,则抛出异常
    // getTypeParameters(): 获取类上的泛型
    checkState(targetType.getTypeParameters().length == 0,
      "不允许含有泛型: %s",
      targetType.getSimpleName());
    // 检查继承的类是否小于等于10,如果大于10则抛出异常
    // getInterfaces(): 获取类继承的类
    checkState(targetType.getInterfaces().length <= NumberConstants.TEN.getIntCode(),
      "最多只允许继承10个类: %s", targetType.getSimpleName());
    // 如果继承了类
    try {
      if (targetType.getInterfaces().length == 1) {
        // 检查继承的类是否有继承其他类,如果超过10个则抛出异常,如果有则抛异常
        checkState(targetType.getInterfaces()[0].getInterfaces().length > 10,
          "不允许多层继承: %s",
          targetType.getSimpleName());
      }
    } catch (Exception e) {
      log.warn("出现了多继承: {}", e.getMessage());
    }
  }

}
